import MessageConstructor from './index.vue'
import { createVNode, render } from 'vue'
import {isVNode} from 'vue'

/**
 * 实例数组，用于控制 脱离文档流的message块不会得加，垂直排序向下 并且上一个消失的时候下一个message块会网上移动
 * 总的来说，实例数组就是其实是用js来控制 message块的所在位置，因为数组中每一个元素代表一个虚拟dom
 */
const instances: any = []

/**
 * seed是id 并不是指 dom的id 而是指标识id 用于找到具体的那个message块然后精准remove 以免对下次的实例数组高度偏移计算造成影响
 */
let seed = 0

/**
 * 用于动态创建 message组件 的函数变量
 */
const Message: any = (opts: any = {}): void =>{
        if (typeof opts === 'string') {
            opts = {
                message: opts,
            }
        }
        //创建一次，id+1
        const id = seed++

        //options 将作为props来给即将创建出来的组件赋值
        let options = opts

        //--用于控制已经创建的message块下移----
        //  如果opts.offset没有值，那么默认值就是20
        let verticalOffset = opts.offset || 20
        //开始遍历实例数组，给每一个虚拟dom的 偏移高度再加上 16   16既间隔
        instances.forEach(({ vm }: any) => {
            verticalOffset += (vm.el.offsetHeight || 0) + 16
        })
        //不管是谁，都加上16 就是让加载的位置 都下移一点点
        verticalOffset += 16
        //---------------------------------

        //props 值开始塞入
        options  = {
            //原本的options里面有核心属性 消息内容和message type主题类型，现在要用...并入新的里面
            ...options,
            //用于style computed计算
            offset: verticalOffset,
            //作为该创建出来的虚拟dom的唯一标识
            id,
            //当内部执行 message块关闭之前，执行这个props function 用于实例数组移除 以及遍历移除某个元素后新实例数组，然后让现存的所有虚拟dom都上移
            beforeClose: () => {
                //这里面的id其实是每个message函数被调用出来的，算是唯一的一个值，如果是共享的变量应该写在message函数外头
                //得到当前要被删除的id 遍历得到该vm在实例数组中的位置
                const index =  instances.findIndex((e: any)=>{
                    const _id = e.vm.component.props.id
                    return id === _id
                })
                //得到位置后，从数组中剔除
                if (index !== -1) {
                    instances.splice(index,1)
                }
                //这时候发现 示例数组长度已经只剩下1了，那么seed 即共享的标识id 重置为0
                if(instances.length === 1){
                    seed = 0
                }
                //得到当前 实例数组的长度
                const len = instances.length
                //如果长度没有，那下面的程序就不再继续执行了，没有可以偏移的了
                if (len < 1) return
                //遍历现有的数组中的元素，然后将现存的元素根据现在位置再上移动一点
                for (let i = index; i < len; i++) {

                    //当前的top - （message块的高度 + 间隔16）
                    const pos =
                        parseInt(instances[i].vm.el.style['top'], 10) - instances[i].vm.el.offsetHeight - 16
                    instances[i].vm.component.props.offset = pos
                }
            },
        }
        //创建 div容器
        const container: any = document.createElement('div')
        const message = options.message

        // 创建vm节点，但是其实还没创建出实例 更没有挂载再node节点中
        const vm: any = createVNode(
            //vue文件本体
            MessageConstructor,
            //props信息
            options,
            //其实这个可以没有
            isVNode(options.message) ? { default: () => message } : null,
        )

        // emit['destroy'] $emit('destroy')
        vm.props.onDestroy = () => {
            //可以毁灭这个元素
            render(null, container)
        }

        //render创建 将虚拟dom用div容器包裹 并创建实例
        render(vm, container)

        //实例数组push 用于后续计算，详细用法请看上头数组的定义
        instances.push({ vm })

        //创建了虚拟dom实例之后 使用dom操作挂在某个地方
        //需要挂载的是容器中的子元素，因为这个子元素才是vm vm能被emit销毁，但是直接append container 。内部的vm被销毁了但是外部的就无法被销毁
        document.body.appendChild(container.firstElementChild)
}

/**
 * 四种状态不断遍历 将一个普通的message函数变成了 额外多上四种key的对象
 */
(['success', 'warning', 'info', 'error'] as const).forEach(type => {
    Message[type] = function (options: any){
        if (typeof options === 'string') {
            options = {
                message: options,
                type,
            }
        } else {
            options.type = type
        }
        return Message(options)
    }
})

export default Message;